/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hxg.portmgr.core.oper.snmp;

import hxg.portmgr.core.arp.Arp;
import hxg.portmgr.core.dao.DaoSource;
import hxg.portmgr.core.device.Device;
import hxg.portmgr.core.device.DeviceModel;
import hxg.portmgr.core.device.intf.Intf;
import hxg.portmgr.core.device.intf.IntfState;
import hxg.portmgr.core.device.intf.IntfType;
import hxg.portmgr.core.ip.IpAddressEntry;
import hxg.portmgr.core.oper.Oper;
import hxg.portmgr.core.oper.OperType;
import hxg.portmgr.core.oper.snmp.oidgroup.FlowOID;
import hxg.portmgr.core.oper.snmp.oidgroup.IfOID;
import hxg.portmgr.core.oper.snmp.oidgroup.IpAddressOID;
import hxg.portmgr.core.oper.snmp.oidgroup.SysOID;
import hxg.portmgr.core.oper.snmp.oidgroup.snmpPing.SnmpPingCtrl;
import hxg.portmgr.core.oper.snmp.record.FlowRecord;
import hxg.portmgr.core.oper.snmp.task.TaskGetMode;
import hxg.portmgr.core.oper.snmp.task.TaskSupport;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;

/**
 *
 * @author hxg
 */
public class SnmpOper implements Oper {

    private static final Logger logger = Logger.getLogger(SnmpOper.class.getName());
    protected String ip;
    protected String readCommunity;
    protected String writeCommunity;
    SnmpOperBatchGetMode batchGetMode = SnmpOperBatchGetMode.GetBulk;
    //protected CommunityTarget target = new CommunityTarget();

    public SnmpOper(String ip, String readCommunity, String writeCommunity) {
        this(ip, 161, readCommunity, writeCommunity, 8000, SnmpConstants.version2c);
    }

    public SnmpOper(String ip, int port, String readCommunity, String writeCommunity, long timeout, int snmpversion) {
        this.ip = ip;
        this.readCommunity = readCommunity == null ? "public" : readCommunity;
        this.writeCommunity = writeCommunity == null ? "private" : writeCommunity;
    }

    public SnmpOperBatchGetMode getBatchGetMode() {
        return batchGetMode;
    }

    public void setBatchGetMode(SnmpOperBatchGetMode batchGetMode) {
        this.batchGetMode = batchGetMode;
    }

    public String getIp() {
        return ip;
    }

    @TaskSupport(chsname = "流量采集", resultGetMode = TaskGetMode.Row, intfClass = Intf.class)
    public FlowOID[] getFlowEntries() {
        return FlowOID.values();
    }

    //<editor-fold defaultstate="collapsed" desc="SNMP读写团体名的管理">
    public String getReadCommunity() {
        return readCommunity;
    }

    public void setReadCommunity(String readCommunity) {
        this.readCommunity = readCommunity;
    }

    public String getWriteCommunity() {
        return writeCommunity;
    }

    public void setWriteCommunity(String writeCommunity) {
        this.writeCommunity = writeCommunity;
    }
    //</editor-fold>

    public Intf[] scanIntfs() throws IOException {
        OID[] oids = new OID[]{IfOID.ifDesc.getOID(), IfOID.ifName.getOID(), IfOID.ifType.getOID()};
        Map<OID, Map<OID, Variable>> mp = SnmpOperUtils.getVariablelTableOfOIDs(oids, SnmpOperUtils.getTarget(ip, readCommunity));
        ArrayList<Intf> inflist = new ArrayList<>();
        for (OID ifidx : mp.keySet()) {
            Intf inf = new Intf();
            inf.setIndex(ifidx.toString());
            Map<OID, Variable> varmp = mp.get(ifidx);
            if (varmp != null && varmp.get(IfOID.ifDesc.getOID()) != null) {
                inf.setDesc(mp.get(ifidx).get(IfOID.ifDesc.getOID()).toString());
            }
            if (varmp != null && varmp.get(IfOID.ifName.getOID()) != null) {
                inf.setName(mp.get(ifidx).get(IfOID.ifName.getOID()).toString());
            }
            if (varmp != null && varmp.get(IfOID.ifType.getOID()) != null) {
                inf.setTypeIntValue(mp.get(ifidx).get(IfOID.ifType.getOID()).toInt());
            }
            Device dev = DaoSource.getDeviceDao().getDeviceByIp(ip);
            if (dev != null) {
                switch (dev.getModel().getIntfMarkFromAttr()) {
                    case All:
                        inf.setMarkName(inf.getIndex() + "-" + inf.getName() + "-" + inf.getDesc()); //"[" + getSeq() + "]" + index + "-" + name + "-" + desc + "-" + markName;
                    case Desc:
                        inf.setMarkName(inf.getDesc()); //return "[" + getSeq() + "]" + desc;
                    case Index:
                        inf.setMarkName(inf.getIndex()); //return "[" + getSeq() + "]" + index;
                    case Name:
                        inf.setMarkName(inf.getName()); //return "[" + getSeq() + "]" + name;
                }
            }
            inflist.add(inf);
        }
        Intf[] infs = new Intf[inflist.size()];
        infs = inflist.toArray(infs);
        buildSequence(infs);
        return infs;
    }
    //依然未成功，待继续测试

    public void SnmpPing(String targetIp) throws IOException {
        OID indexOID = new OID(".1");
        VariableBinding[] vbs = new VariableBinding[]{
            new VariableBinding(SnmpPingCtrl.pingCtlTestName.getAppendOID(indexOID), new OctetString("Test")),
            new VariableBinding(SnmpPingCtrl.pingCtlTargetAddressType.getAppendOID(indexOID), new Integer32(1)),
            new VariableBinding(SnmpPingCtrl.pingCtlTargetAddress.getAppendOID(indexOID), new OctetString(targetIp))
        };
        SnmpOperUtils.set(SnmpPingCtrl.pingCtlRowStatus.getAppendOID(indexOID), 4, SnmpOperUtils.getTarget(ip, writeCommunity));
        SnmpOperUtils.set(vbs, SnmpOperUtils.getTarget(ip, writeCommunity));
        SnmpOperUtils.set(SnmpPingCtrl.pingCtlRowStatus.getAppendOID(indexOID), 1, SnmpOperUtils.getTarget(ip, writeCommunity));
    }

    /**
     * 建立序列的方法，按SNMP的INDEX顺序
     *
     * @param infs
     */
    protected void buildSequence(Intf[] infs) {
        EnumMap<IntfType, AtomicInteger> seqMap = new EnumMap(IntfType.class);
        for (IntfType type : IntfType.values()) {
            seqMap.put(type, new AtomicInteger(1));
        }
        for (Intf inf : infs) {
            inf.setSequence(seqMap.get(inf.getType()).getAndIncrement());
        }
    }

    public boolean isReachAble() {
        return SnmpOperUtils.isReachAble(SnmpOperUtils.getTarget(ip, readCommunity));
    }

    public ArrayList<Arp> getIpNetToMediaTable() throws IOException {
        OID[] oids = new OID[4];
        oids[0] = new OID(Arp.InNetToMediaIndex);
        oids[1] = new OID(Arp.InNetToMediaIpAddr);
        oids[2] = new OID(Arp.InNetToMediaMacAddr);
        oids[3] = new OID(Arp.InNetToMediaType);
        Map<OID, Map<OID, Variable>> mp = SnmpOperUtils.getVariablelTableOfOIDs(oids, SnmpOperUtils.getTarget(ip, readCommunity));
        ArrayList<Arp> arps = new ArrayList<>();
        for (Map<OID, Variable> arpmp : mp.values()) {
            Arp arp = new Arp();
            arp.setIntfIndex(arpmp.get(oids[0]).toString());
            arp.setIpAddr(arpmp.get(oids[1]).toString());
            arp.setMacAddr(arpmp.get(oids[2]).toString());
            arp.setType(arpmp.get(oids[3]).toInt());
            arps.add(arp);
        }
        return arps;
    }

    public void setName(String name) throws IOException {
        SnmpOperUtils.set(SysOID.sysName.getOID(), name, SnmpOperUtils.getTarget(ip, writeCommunity));
    }

    //<editor-fold defaultstate="collapsed" desc="获取接口状态，端口状态在枚举IntfState中">
    /**
     *
     * @param infs
     * @return 第一个是AdminState，第二个才是OperState
     * @throws IOException
     */
    public LinkedHashMap<Intf, IntfState[]> getIntfState(Intf[] infs) throws IOException {
        LinkedHashMap<Intf, IntfState[]> intfStateMp = new LinkedHashMap<>();
        CommunityTarget target = SnmpOperUtils.getTarget(ip, readCommunity);
        IfOID[] entries = new IfOID[]{IfOID.ifAdminState, IfOID.ifOperState};
        String[] ifidxs = new String[infs.length];
        for (int i = 0; i < infs.length; i++) {
            ifidxs[i] = infs[i].getIndex();
        }
        Map<IfOID, Variable>[] mps = SnmpOperUtils.getIntfVariblesOfIntf(ifidxs, entries, target);
        for (int i = 0; i < infs.length; i++) {
            Map<IfOID, Variable> mp = mps[i];
            IntfState adminState = IntfState.UnKnown;
            IntfState operState = IntfState.UnKnown;
            if (mp.containsKey(IfOID.ifAdminState)) {
                adminState = IntfAdminStateFromIntValue(mp.get(IfOID.ifAdminState).toInt());
            }
            if (mp.containsKey(IfOID.ifOperState)) {
                operState = IntfOperStateFromIntValue(mp.get(IfOID.ifOperState).toInt());
            }
            intfStateMp.put(infs[i], new IntfState[]{operState, adminState});
        }
        return intfStateMp;
    }

    public LinkedHashMap<Intf, IntfState[]> getIntfState() throws IOException {
        return getIntfState(DaoSource.getDeviceDao().getDeviceByIp(ip).getIntfs());
    }

    protected IntfState IntfAdminStateFromIntValue(int adminState) {
        switch (adminState) {
            case 1:
                return IntfState.AdminUp;
            case 2:
                return IntfState.AdminDown;
            default:
                return IntfState.UnKnown;
        }
    }

    protected IntfState IntfOperStateFromIntValue(int operState) {
        switch (operState) {
            case 1:
                return IntfState.OperUp;
            case 2:
                return IntfState.OperDown;
            default:
                return IntfState.UnKnown;
        }
    }

    public IntfState getIntfState(Intf inf) throws IOException {
        return getIntfState(inf.getIndex());
    }

    public IntfState getIntfState(String ifidx) throws IOException {
        IntfState operState = getOperState(ifidx);
        switch (operState) {
            case OperUp:
                return operState;
            case OperDown: {
                IntfState adminState = getAdminState(ifidx);
                return (adminState == IntfState.AdminUp) ? IntfState.OperDown : IntfState.AdminDown;
            }
            default:
                return IntfState.UnKnown;
        }
    }

    protected IntfState getAdminState(String ifidx) throws IOException {
        Variable var = SnmpOperUtils.get(IfOID.ifAdminState.oidValue() + "." + ifidx, SnmpOperUtils.getTarget(ip, readCommunity));
        int val = -1;
        if (var != null) {
            val = SnmpOperUtils.get(IfOID.ifAdminState.oidValue() + "." + ifidx, SnmpOperUtils.getTarget(ip, readCommunity)).toInt();
        }
        return IntfAdminStateFromIntValue(val);
    }

    protected IntfState getOperState(String ifidx) throws IOException {
        int val = SnmpOperUtils.get(IfOID.ifOperState.oidValue() + "." + ifidx, SnmpOperUtils.getTarget(ip, readCommunity)).toInt();
        return IntfOperStateFromIntValue(val);
    }
//</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="端口操作[打开，关闭]">
    public void ShutdownPort(String ifidx) throws IOException {
        //SnmpEntity se = new SnmpEntity(ip, 161, writeCommunity);
        //se.setCommunity(this.getWriteCommunity());
        SnmpOperUtils.set(IfOID.ifAdminState.oidValue() + "." + ifidx, 2, SnmpOperUtils.getTarget(ip, writeCommunity));
        //se.close();
    }

    public void TurnOnPort(String ifidx) throws IOException {
        //SnmpEntity se = new SnmpEntity(ip, 161, writeCommunity);
        //se.setCommunity(this.getWriteCommunity());
        SnmpOperUtils.set(IfOID.ifAdminState.oidValue() + "." + ifidx, 1, SnmpOperUtils.getTarget(ip, writeCommunity));
        //se.close();
    }
    //</editor-fold>

    final public FlowRecord getFlowRecord(Intf inf, FlowOID[] flowOids) throws IOException {
        FlowRecord record = new FlowRecord(inf, getFlowCount(inf.getIndex(), flowOids), System.currentTimeMillis());
        logger.log(Level.INFO, record.toString());
        return record;
    }

    final protected EnumMap<FlowOID, Long> getFlowCount(String ifidx, FlowOID[] flowOids) throws IOException {
        EnumMap<FlowOID, Long> mp = new EnumMap<>(FlowOID.class);
        OID[] oids = new OID[flowOids.length];
        for (int i = 0; i < flowOids.length; i++) {
            oids[i] = new OID(flowOids[i].oidValue() + "." + ifidx);
        }
        Map<OID, Variable> varmp = SnmpOperUtils.getVariablesofOIDs(oids, SnmpOperUtils.getTarget(ip, readCommunity));
        for (int i = 0; i < oids.length; i++) {
            mp.put(flowOids[i], varmp.get(oids[i]).toLong());
        }
        return mp;
    }

    final public Map<String, EnumMap<FlowOID, Long>> getFlowCountTable(FlowOID[] flowOids) throws IOException {
        Map<String, EnumMap<FlowOID, Long>> mp = new LinkedHashMap<>();
        OID[] oids = new OID[flowOids.length];
        for (int i = 0; i < flowOids.length; i++) {
            oids[i] = flowOids[i].getOID();
        }
        Map<OID, Map<OID, Variable>> ifresmp = SnmpOperUtils.getVariablelTableOfOIDs(oids, new int[]{6, 6}, SnmpOperUtils.getTarget(ip, readCommunity));
        for (OID ifidx : ifresmp.keySet()) {
            Map<OID, Variable> varmp = ifresmp.get(ifidx);
            EnumMap<FlowOID, Long> resmp = new EnumMap<>(FlowOID.class);
            for (int i = 0; i < flowOids.length; i++) {
                if (varmp.containsKey(flowOids[i].getOID())) {
                    resmp.put(flowOids[i], varmp.get(flowOids[i].getOID()).toLong());
                }
            }
            mp.put(ifidx.toString(), resmp);
        }
        return mp;
    }

    public static void scanDeviceModel(Device dev) throws IOException {
        //SnmpOper oper = dev.getSnmpOper();

        CommunityTarget target = SnmpOperUtils.getTarget(dev.getIp(), dev.getSnmpReadCommunity());
//        if (target == null) {
//            System.out.println("IP=" + dev.getIp() + "|Read=" + dev.getSnmpReadCommunity());
//        }
        Variable var = SnmpOperUtils.get(SysOID.sysObjectId.getOID(), target);
        if (var != null) {
            DeviceModel model = DaoSource.getDeviceModelDAO().getModelBySysOid(var.toString());
            dev.setModel(model);
        } else {
            dev.setModel(DeviceModel.getUnReachedAbleModel());
        }

    }

    //<editor-fold defaultstate="collapsed" desc="静态方法 设备扫描">
    public static Device scanDevice(String ip, String readCommunity, int timeout) throws IOException {
        if (readCommunity == null) {
            readCommunity = "public";
        }
        Device dev = new Device();
        dev.setIp(ip);
        SnmpOper oper = (SnmpOper) dev.getOper(OperType.SnmpOper);
        if (oper.isReachAble()) {
            logger.log(Level.INFO, "IP[{0}]可达，开始扫描", new Object[]{ip});
            OID[] sysOids = new OID[]{
                SysOID.sysDescr.getOID(), SysOID.sysName.getOID(), SysOID.sysObjectId.getOID()};
            Map<OID, Variable> mp = SnmpOperUtils.getVariablesofOIDs(sysOids, SnmpOperUtils.getTarget(ip, readCommunity));
            Variable sysOid = mp.get(SysOID.sysObjectId.getOID());
            Variable name = mp.get(SysOID.sysName.getOID());
            Variable desc = mp.get(SysOID.sysDescr.getOID());
            //dev.setIp(ip);
            dev.setName(name.toString());
            dev.setDesc(desc.toString());
            dev.setSysOid(sysOid.toString());
            dev.setSnmpReadCommunity(readCommunity);
            if (dev.getChsName() == null || dev.getChsName().isEmpty()) {
                if (name.toString().isEmpty()) {
                    dev.setChsName(desc.toString());
                } else {
                    dev.setChsName(name.toString());
                }
            }
            IpAddressEntry[] ips = scanDeviceMultipleIp(ip, readCommunity);
            if (ips == null || ips.length == 0) {
                ips = new IpAddressEntry[]{new IpAddressEntry(ip, "255.255.255.255")};
                dev.setIps(ips);
                for (IpAddressEntry ipe : ips) {
                    if (ipe.getNetMask().equals("255.255.255.255")) {
                        dev.setIp(ipe.getIp());
                    }
                }
            } else {
                dev.setIps(ips);
                if (!hxg.portmgr.core.ip.IpAddress.isSiteLocalAddress(ip)) {
                    dev.resetDefaultManagerIP();
                }
            }
            //dev.setState(DeviceState.Online);
            return dev;
        } else {
            //dev.setState(DeviceState.OffLine);
            return dev;
        }
        //return new Device();
    }

    public static IpAddressEntry[] scanDeviceMultipleIp(String ip, String readCommunity) throws IOException {
        OID[] oids = new OID[]{IpAddressOID.NetMask.getOID(), IpAddressOID.IpAddrIfIndex.getOID()};
        Map<OID, Map<OID, Variable>> mp = SnmpOperUtils.getVariablelTableOfOIDs(oids, SnmpOperUtils.getTarget(ip, readCommunity));
        IpAddressEntry[] ips = new IpAddressEntry[mp.size()];
        ArrayList<IpAddressEntry> iplist = new ArrayList<>();
        for (OID ipidx : mp.keySet()) {
            Map<OID, Variable> varmp = mp.get(ipidx);
            IpAddressEntry ipaddrentry = new IpAddressEntry(ipidx.toString(), varmp.get(IpAddressOID.NetMask.getOID()).toString());
            ipaddrentry.setIpAddrIfIndex(varmp.get(IpAddressOID.IpAddrIfIndex.getOID()).toString());
            iplist.add(ipaddrentry);
        }
        ips = iplist.toArray(ips);
        return ips;
    }

    public static SnmpOper getSnmpOper(Device dev) {
        String read = dev.getSnmpReadCommunity(), write = dev.getSnmpWriteCommunity();
        Class<? extends Oper> operclass = dev.getModel().getOperclass();
        if (operclass != null) {
            try {
                Constructor<SnmpOper> con = (Constructor<SnmpOper>) operclass.getConstructor(String.class, String.class, String.class);
                SnmpOper oper = con.newInstance(dev.getIp(), read, write);
                return oper;
            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                logger.log(Level.SEVERE, null, ex);
                return null;
            }
        } else {
            return dev.getModel().getType().getDefaultSnmpOper(dev.getIp(), read, write);
        }
    }
}
